using System;
using System.Collections.Generic;
using System.Linq;
using TeamCityMonitor.Models;
using TeamCitySharp.DomainEntities;

namespace TeamCityMonitor.Repository.Caches
{
    public interface ITeamCityRepository
    {
        Server GetServerInfo();
        List<ProjectModel> GetAllTroubledProjectModels();
        IEnumerable<BuildConfig> GetBuildConfigurations();

        int GetNoOfProjects();
        int GetNoOfConfigurations();
        IEnumerable<User> GetUsers();
        int GetNoOfSuccessfullBuilds();
        int GetNoOfFailingBuilds();
        int GetNoOfErrorBuilds();

        int GetNoOfBuildsByUser(User user);
        int GetNoOfSuccessfullBuildsByUser(User user);
        int GetNoOfFailingBuildsByUser(User user);
        int GetNoOfErrorBuildsByUser(User user);

        int GetNoOfBuildsByBuildConfig(BuildConfig config);
        int GetNoOfSuccessfullBuildsByBuildConfig(BuildConfig config);
        int GetNoOfFailingBuildsByBuildConfig(BuildConfig config);
        int GetNoOfErrorBuildsByBuildConfig(BuildConfig config);
    }

    public class TeamCityRepository : ITeamCityRepository
    {
        private readonly IUserPictureRepository _userPictureRepository;
        private readonly ITeamCityCacheRefresher _teamCityCacheRefresher;

 
        public TeamCityRepository(IUserPictureRepository userPictureRepository)
        {
            _userPictureRepository = userPictureRepository;
            _teamCityCacheRefresher = new TeamCityCacheRefresher();
        }


        public Server GetServerInfo()
        {
            return TeamCityCacheRefresher.DataCache.ServerInformation;
        }

        private List<TroubleEntry> GetTroublesEntities()
        {
            var troubles = new List<TroubleEntry>();
            var projects = TeamCityCacheRefresher.DataCache.ActiveProjects.ToList();

            GetTroubledBuilds().ForEach(build => troubles.Add(new TroubleEntry
            {
                Project = projects.First(project => project.Id == build.BuildConfig.ProjectId),
                Config = build.BuildConfig,
                Build = build
            }));

            return troubles;
        }

        private List<Build> GetTroubledBuilds()
        {
            var latestBuilds = TeamCityCacheRefresher.DataCache.LatestBuilds.ToList();

            latestBuilds = HandleBuildsInUnknownStates(latestBuilds);
            latestBuilds = HandleBuildsWhichHaveNeverBeenBuild(latestBuilds);

            return latestBuilds.Where(build => build.Status != "SUCCESS").ToList();
        }

        private List<Build> HandleBuildsInUnknownStates(List<Build> builds)
        {
            if (Properties.Settings.Default.ShowBuildsWhichAreInAnUnknownState == true)
            {
                return builds;
            }
            return builds.Where(build => build.Status != "UNKNOWN").ToList();
        }

        private List<Build> HandleBuildsWhichHaveNeverBeenBuild(List<Build> builds)
        {
            if (Properties.Settings.Default.ShowBuildConfigsWhichHaveNeverBeenBuild == true)
            {
                return builds;
            }
            return builds.Where(build => build.Status != "NEVERBUILD").ToList();
        }

        public List<ProjectModel> GetAllTroubledProjectModels()
        {
            return GetTroublesEntities().Select(troubledEntry => GenerateProjectModelFromConfigAndProject(troubledEntry)).ToList();
        }

        public IEnumerable<BuildConfig> GetBuildConfigurations()
        {
            return TeamCityCacheRefresher.DataCache.BuildConfigurations;
        }

        private ProjectModel GenerateProjectModelFromConfigAndProject(TroubleEntry entry)
        {
            var change = GetChangeToDisplay(entry.Build);

            var model = new ProjectModel()
            {
                LastBuildStatus = (entry.Build.Status ?? "UNKNOWN"),
                LastBuildStatusText = (entry.Build.StatusText ?? ""),
                BuildStatusCause = GetBuildStatusCause(entry.Build),
                Title = entry.Project.Name,
                SubTitle = entry.Config.Name,
                LastChangeUser = GetUsername(change),
                LastChangeComment = GetLastChangeComment(change),
                LastChangeUserPictureUrl = _userPictureRepository.GetUserPicture(change.Username),
                BuildNumber = entry.Build.Number,
                Build = entry.Build.Id,
                BuildConfig = entry.Config.Id,
                Project = entry.Project.Id,
                IsRunning = GetRunningStatus(entry.Build),
            };

            return model;            
        }

        private Change GetChangeToDisplay(Build build)
        {
            var usersWhoseChangesWeShouldDisregard = Properties.Settings.Default.DisregardChangesFromTheseUsers.Split(new [] {" "}, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>();

            var changes = build.Changes.Change.OrderByDescending(change => change.Date).ToList();

            var changesUntilFirstBuildByValidUser = changes.TakeWhile(change => usersWhoseChangesWeShouldDisregard.Contains(change.Username))
                                                                                .ToList();
            changes.RemoveAll(changesUntilFirstBuildByValidUser.Contains);

            if(changesUntilFirstBuildByValidUser.Count > 0)
            {
                var change = changes.First();
                change.Username = string.Format("{0} ({1} newer changes ignored)", change.Username,
                                                changesUntilFirstBuildByValidUser.Count);
                return change;
            }
 
            return changes.First();
        }

        private bool GetRunningStatus(Build build)
        {
            var builds = TeamCityCacheRefresher.DataCache.RunningBuilds.ToList();
            return builds.Any(bld => bld.BuildTypeId == build.BuildTypeId);
        }

        private string GetLastChangeComment(Change change)
        {
            if (change != null)
            {
                return (string.IsNullOrEmpty(change.Comment) == false
                            ? string.Format("Last commit log: {0}", change.Comment)
                            : "Last commit log: (No commit log found)");
            }
            return "Last commit log: (No commit log found)";
        }

        private string GetUsername(Change change)
        {
            if (change != null)
            {
                return string.Format("{0}{1}", Properties.Settings.Default.BuildTroublesMessage, change.Username);
            }
            return string.Format("{0}{1}",Properties.Settings.Default.BuildTroublesMessage, "(No history avaliable)");
        }

        public int GetNoOfProjects()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfProjects;
        }

        public int GetNoOfConfigurations()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfConfigurations;
        }

        public int GetNoOfBuilds()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfBuilds;
        }

        private string GetBuildStatusCause(Build build)
        {
            var cause = "";

            switch (build.Status)
            {
                case "SUCCESS":
                    break;
                case "FAILURE":
                    cause = "(Failing)";
                    break;
                case "ERROR":
                    cause = "(Error)";
                    break;
                case "NEVERBUILD":
                    cause = "(Never build)";
                    break;
                case "UNKNOWN":
                    cause = "(Unknown)";
                    break;
                default:
                    cause = "(Unknown)";
                    break;
            }

            return cause;
        }

        public IEnumerable<User> GetUsers()
        {
            return TeamCityCacheRefresher.DataCache.Users.ToList();
        }

        public int GetNoOfSuccessfullBuilds()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfSuccessfullBuilds;
        }

        public int GetNoOfFailingBuilds()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfFailingBuilds;
        }

        public int GetNoOfErrorBuilds()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfErrorBuilds;
        }

        public int GetNoOfUnknownBuilds()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfUnknownBuilds;
        }

        public int GetNoOfBuildsSince()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfBuildsSince;
        }

        public int GetNoOfSuccessfullBuildsSince()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfSuccessfullBuildsSince;
        }

        public int GetNoOfFailingBuildsSince()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfFailingBuildsSince;
        }

        public int GetNoOfErrorBuildsSince()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfErrorBuildsSince;
        }

        public object GetNoOfUnknownBuildsSince()
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfUnknownBuildsSince;
        }

        public int GetNoOfBuildsByUser(User user)
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfBuildsByUser.ContainsKey(user.Id)
                       ? TeamCityCacheRefresher.StatisticsCache.NoOfBuildsByUser[user.Id]
                       : 0;
        }

        public int GetNoOfSuccessfullBuildsByUser(User user)
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfSuccessfullBuildsByUser.ContainsKey(user.Id)
                       ? TeamCityCacheRefresher.StatisticsCache.NoOfSuccessfullBuildsByUser[user.Id]
                       : 0;
        }

        public int GetNoOfFailingBuildsByUser(User user)
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfFailingBuildsByUser.ContainsKey(user.Id)
                       ? TeamCityCacheRefresher.StatisticsCache.NoOfFailingBuildsByUser[user.Id]
                       : 0;
        }

        public int GetNoOfErrorBuildsByUser(User user)
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfErrorBuildsByUser.ContainsKey(user.Id)
                       ? TeamCityCacheRefresher.StatisticsCache.NoOfErrorBuildsByUser[user.Id]
                       : 0;
        }
        public int GetNoOfSuccessfullBuildsByBuildConfig(BuildConfig config)
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfSuccessfullBuildsByBuildConfig.ContainsKey(config.Id)
                       ? TeamCityCacheRefresher.StatisticsCache.NoOfSuccessfullBuildsByBuildConfig[config.Id]
                       : 0;
        }

        public int GetNoOfFailingBuildsByBuildConfig(BuildConfig config)
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfFailingBuildsByBuildConfig.ContainsKey(config.Id)
                       ? TeamCityCacheRefresher.StatisticsCache.NoOfFailingBuildsByBuildConfig[config.Id]
                       : 0;
        }

        public int GetNoOfErrorBuildsByBuildConfig(BuildConfig config)
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfErrorBuildsByBuildConfig.ContainsKey(config.Id)
                       ? TeamCityCacheRefresher.StatisticsCache.NoOfErrorBuildsByBuildConfig[config.Id]
                       : 0;
        }

        public int GetNoOfBuildsByBuildConfig(BuildConfig config)
        {
            return TeamCityCacheRefresher.StatisticsCache.NoOfBuildsByBuildConfig.ContainsKey(config.Id)
                       ? TeamCityCacheRefresher.StatisticsCache.NoOfBuildsByBuildConfig[config.Id]
                       : 0;
        }

        private class TroubleEntry
        {
            public Project Project { get; set; }
            public BuildConfig Config { get; set; }
            public Build Build { get; set; }
        }
    }
}